/****************************************************************************

    Copyright 2008, 2009  Clark Scheff
    
    This file is part of G1 Psycho.

    G1 Psycho is free software: you can redistribute it and/or
    modify it under the terms of the GNU General Public License as published
    by the Free Software Foundation version 3 of the License.

    G1 Psycho is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with G1 Psycho. If not, see http://www.gnu.org/licenses
    
****************************************************************************/

package com.android.app.psycho;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import java.util.Random;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;

import java.io.IOException;
import java.lang.Math;
import java.util.Vector;

/**
 * @author lithium
 *
 */
public class GraphView extends View implements Runnable
{
	public static final String APP_NAME = "G1 Psycho";
	
    public static final int SWING_DELAY = 500;
    public static final int MAX_COLORS = 8;
    public static final int NUM_SAMPLES = 3;
    public static final float ACCEL_THRESHOLD = 1.2f;
    
    public static final int NO_CHANGE = 0;
    public static final int SWING_DETECTED = 1;
    public static final int HIT_DETECTED = 2;
    public static final int NO_MOVEMENT = 3;
    public static final float PI_OVER_180 = 0.017453293f;
    
    private Bitmap  mBitmap;
    private Bitmap  mKnife;
    private Bitmap	mBackground;
    private Bitmap  mBackSplatter;
    private Bitmap  mSplatter;
    private Paint   mPaint = new Paint();
    private Canvas  mCanvas = new Canvas();
    private Canvas  mBathTile = new Canvas();
    private int		mWidth;
    private float   mHeight;
    private boolean mTouched = false;
    private MediaPlayer mMP = new MediaPlayer();
    private SensorManager mSensorManager = null;
    private Vector<Float> mMagnitudes = new Vector<Float>();
    private int mRotation = 0;
    private int mRotateInc = 1;
    private boolean mSlashing = false;
    private boolean mShowBlood = true;
    private boolean mLefty = false;
    private Thread mThread = null;
    private boolean mPaused = false;
    private int mMusicPosition = 0;
    Context mContext;
    private WakeLock mWakeLock = null;
    private boolean mKeepScreenOn = true;
    private boolean mLoopStrings = true;
    private float mSenseOffset = 5.0f;
    
    public GraphView(Context context, SensorManager sm) {
        super(context);
        mContext = context;
        mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    	mPaint.setFilterBitmap(true);

        mKnife = BitmapFactory.decodeResource(getResources(), R.drawable.knife);
        mBackground = BitmapFactory.decodeResource(getResources(), R.drawable.background);
        mSplatter = BitmapFactory.decodeResource(getResources(), R.drawable.bloodsplatter);
        mSensorManager = (SensorManager)sm;

		mMP = MediaPlayer.create(mContext, R.raw.psycho);
        mMP.setLooping(true);
        
        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 
        this.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "G1Psycho");
        mKeepScreenOn = this.getKeepScreenOn();
        this.setKeepScreenOn(true);

        mThread = new Thread(this);
        mThread.start();
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
        mCanvas.setBitmap(mBitmap);
        mCanvas.drawColor(0xFF000000);
        mBackSplatter = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mBathTile.setBitmap(mBackSplatter);
        mWidth = w;
        mHeight = h;
        clearBlood();

        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        synchronized (this) {
            if (mBitmap != null) {
            	drawScene(canvas);
            }
        }
    }
    
    public void onStop() {
    	// last unregister the sensor listener.
    	mSensorManager.unregisterListener(mListener);

    	// if there is any sound playing, stop it.
    	if(mMP.isPlaying() == true)
    		mMP.stop();

    	try {
    		mMP.prepare();
    	} catch (IllegalStateException e) {
    		e.printStackTrace();
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	
    	// now release the media player
    	mMP.release();
    	
    	// clear out the vector of magnitudes
    	mMagnitudes.clear();
    	
 	   	mPaused = true;
        this.setKeepScreenOn(mKeepScreenOn);
    }
 
    private final SensorListener mListener = new SensorListener() {
        public void onSensorChanged(int sensor, float[] values) {
       		float magnitude = (float)Math.sqrt(values[0]*values[0]+values[1]*values[1]+values[2]*values[2]);

       		int movement = updateAccelReadings(magnitude);
       		Random rand = new Random();
   			if(movement == SWING_DETECTED && mTouched && !mPaused) {
   				if(false == mMP.isPlaying())
   					mMP.start();
   				else if(mLoopStrings && mMP.getCurrentPosition() >= 23900)
   					mMP.seekTo(0);
   				mSlashing = true;
   	       		if(mShowBlood && rand.nextInt(3) == 2)
   	       			addSplatter(values[0],values[1],magnitude);
    		} else if(false == mTouched || movement == NO_MOVEMENT) {
       			if(!mPaused && true == mMP.isPlaying())
       				mMP.pause();
       			mRotation = 0;
       			mSlashing = false;
       		}
        }
        
        public void onAccuracyChanged(int x, int y) {

        }
    };

    // this method will execute once thread.start is called.  This method
    // will notify the handler that it is time to update the game
    public void run() {
    	while(true) {
   			handler.sendEmptyMessage(0);
   			try {
				// allow the thread to sleep a bit and allow other threads to run
   				// 17 milliseconds will allow for a frame rate of about 60 FPS.
   				Thread.sleep(20);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }
    
    private Handler handler = new Handler() {
    	@Override
    	public void handleMessage(Message msg) {
    		if(mSlashing)
    			mRotation += mRotateInc;
    		if(mRotation > 45 || mRotation < -45) {
    			mRotateInc = -mRotateInc;
    			mRotation += mRotateInc;
    		}
    		
    		invalidate();
    	}
    };
    
    @Override
	public boolean onTouchEvent(MotionEvent event) {
    	int action = event.getAction();
//    	int x = (int)event.getX();
//    	int y = (int)event.getY();
//    	int histX = (int)event.getHistoricalX(event.getHistorySize()-1);
//    	int histY = (int)event.getHistoricalY(event.getHistorySize()-1);
    	if(action == MotionEvent.ACTION_DOWN) {
    		mTouched = !mTouched;
    		
    		if(mTouched) {
    			int mask = 0;
    			mask = SensorManager.SENSOR_ACCELEROMETER;// | SensorManager.SENSOR_ORIENTATION;
    			mSensorManager.registerListener(mListener, mask, SensorManager.SENSOR_DELAY_GAME);
    			Toast.makeText(mContext, R.string.note_slash_away, Toast.LENGTH_SHORT).show();
    		} else {
    			mSensorManager.unregisterListener(mListener);
    			mSlashing = false;
    			Toast.makeText(mContext, R.string.note_slashing_disabled, Toast.LENGTH_SHORT).show();
    		}
    	}
   	
    	return true;
    }

    private int updateAccelReadings(float magnitude) {
    	if(mMagnitudes.size() == NUM_SAMPLES)
    		mMagnitudes.removeElementAt(0);
    	
    	mMagnitudes.add(mMagnitudes.size(), magnitude);
    	
    	if(mMagnitudes.size() < NUM_SAMPLES)
    		return NO_CHANGE;
    	
    	int moved = 0;
    	int stopped = 0;
    	int state = NO_CHANGE;

		for(int i = 0; i < mMagnitudes.size(); i++) {
    		float mag = mMagnitudes.get(i);
    		
    		if(mag < (SensorManager.GRAVITY_EARTH-mSenseOffset) || mag > (SensorManager.GRAVITY_EARTH+mSenseOffset))
    			moved++;
    		else
    			stopped++;
    	}
    	
    	if(moved == NUM_SAMPLES) {
    		state = SWING_DETECTED;
    	} else if(stopped == NUM_SAMPLES) {
   			state = NO_MOVEMENT;
    	}
    	
    	return state;
    }
    
   private void drawScene(Canvas canvas) {
	   Canvas newCanvas = new Canvas();
	   newCanvas.setBitmap(mBackSplatter);
	   canvas.drawBitmap(mBackSplatter, 0, 0, mPaint);
//	   canvas.drawBitmap(mFaucet, mWidth-mFaucet.getWidth()-2, 0, null);

	   int width = mKnife.getWidth()/2;
	   int height =mKnife.getHeight()/2;
	   Matrix m = new Matrix();
	   if(true == mLefty)
		   m.preScale(-1.0f, 1.0f, width, height);
	   m.postRotate(mRotation, width, height);
	   m.postTranslate(mWidth/2 - width, mHeight/2 - height);
	   
//	   if(true == mLefty) {
//		   canvas.drawBitmap(mKnifeLeft, m, mPaint);
//	   } else {
		   canvas.drawBitmap(mKnife, m, mPaint);
//	   }
	   
	   if(mTouched) {
		   mPaint.setColor(0xFF00FF00);
		   canvas.drawCircle(5, 5, 4, mPaint);
	   }
    }
   
   private void addSplatter(float xAccel, float yAccel, float magnitude) {
	   Random rand = new Random();
	   int x = rand.nextInt(mBackSplatter.getWidth());
	   int y = rand.nextInt(mBackSplatter.getHeight());
	   
	   Matrix m = new Matrix();

	   float scale;// = magnitude / (SensorManager.GRAVITY_EARTH * 2.0f);
	   float flipX = 1.0f;
	   float flipY = 1.0f;
	   
	   if(rand.nextInt(2) == 1)
		   flipX = -1.0f;
	   if(rand.nextInt(2) == 1)
		   flipY = -1.0f;
	   
	   scale = rand.nextFloat() * 2.0f;
	   m.preScale(scale*flipX, scale*flipY);
	   m.postRotate(rand.nextInt(570), mBackSplatter.getWidth()/2, mBackSplatter.getHeight()/2);
	   m.postTranslate(x, y);
	   
	   if(mBackSplatter != null) {
		   mBathTile.setBitmap(mBackSplatter);
		   mBathTile.drawBitmap(mSplatter, m, mPaint);
	   }
   }
   
   public void clearBlood() {
//       mBathTile.drawBitmap(mBackground, 0, 0, null);
       for(int x = 0; x < 320; x+=80)
    	   for(int y = 0; y < 480; y+=80) {
    		   Matrix m = new Matrix();
    		   m.postTranslate(x, y);
    		   mBathTile.drawBitmap(mBackground, m, mPaint);
    	   }
   }
   
   public void setShowBlood(boolean show) {
	   mShowBlood = show;
   }
   
   public boolean getShowBlood() {
	   return mShowBlood;
   }
   
   public void resetMusic() {
	   mMP.seekTo(0);
   }
   
   public void setLefty(boolean lefty) {
	   mLefty = lefty;
   }
   
   public boolean getLefty() {
	   return mLefty;
   }
   
   public void setLoopStrings(boolean loop) {
	   mLoopStrings = loop;
   }
   
   public boolean getLoopStrings() {
	   return mLoopStrings;
   }
   
   public void setSenseOffset(float offset) {
	   mSenseOffset = (offset+1) / 3.0f;
   }
   
   public float getSenseOffset() {
	   return mSenseOffset * 3.0f - 1;
   }
   
   public void pause() {
	   mPaused = true;
       this.setKeepScreenOn(mKeepScreenOn);
	   mSensorManager.unregisterListener(mListener);
	   mThread.suspend();
	   mMusicPosition = mMP.getCurrentPosition();
	   // if there is any sound playing, stop it.
   		if(mMP.isPlaying() == true)
   			mMP.stop();

   		try {
   			mMP.prepare();
   		} catch (IllegalStateException e) {
   			e.printStackTrace();
   		} catch (IOException e) {
   			e.printStackTrace();
   		}
   	
   		// now release the media player
   		mMP.release();
   }
   
   public void resume() {
	   mPaused = false;
	   this.setKeepScreenOn(true);
	   mMP = new MediaPlayer();
	   mMP = MediaPlayer.create(mContext, R.raw.psycho);
       mMP.setLooping(true);
       mMP.start();
       mMP.pause();
       mMP.seekTo(mMusicPosition);
       
	   mThread.resume();
	   if(mTouched)
		   mSensorManager.registerListener(mListener, SensorManager.SENSOR_ACCELEROMETER, 
				   							SensorManager.SENSOR_DELAY_GAME);
   }
}

